using System.Collections.ObjectModel;
using Avalonia.Controls.Primitives;
using GraphShape.Algorithms.Layout;

namespace GraphShape.Controls
{
    /// <summary>
    /// Compound vertex control.
    /// </summary>
    [TemplatePart(Name = PartInnerCanvas, Type = typeof(Control))]
    public class CompoundVertexControl : VertexControl, ICompoundVertexControl
    {
        static CompoundVertexControl()
        {
            // Register a class handler for the GraphCanvas.PositionChanged routed event
            GraphCanvas.PositionChangedEvent.AddClassHandler<CompoundVertexControl>(OnPositionChanged);
            

            IsExpandedProperty.Changed.Subscribe(OnIsExpandedPropertyChanged);
        }

        /// <summary>
        /// Inner canvas part name.
        /// </summary>
        protected const string PartInnerCanvas = "PART_InnerCanvas";

        /// <summary>
        /// Inner canvas control.
        /// </summary>
        protected Control InnerCanvas;

        private bool _activePositionChangeReaction;

        /// <summary>
        /// Initializes a new instance of the <see cref="CompoundVertexControl"/> class.
        /// </summary>
        public CompoundVertexControl()
        {
            Vertices = new ObservableCollection<VertexControl>();
        }

        protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
        {
            InnerCanvas = e.NameScope.Find<Control>(PartInnerCanvas);
            base.OnApplyTemplate(e);
        }


        #region Dependency Properties

        #region Vertices

        private ObservableCollection<VertexControl> _vertices;

        /// <summary>
        /// Vertices controls.
        /// </summary>
        public ObservableCollection<VertexControl> Vertices
        {
            get => GetValue(VerticesProperty);
            protected set => SetAndRaise(VerticesProperty, ref _vertices, value);
        }

        /// <summary>
        /// Vertices property key.
        /// </summary>
        [NotNull]
        protected static readonly DirectProperty<CompoundVertexControl, ObservableCollection<VertexControl>> VerticesProperty
            = AvaloniaProperty.RegisterDirect<CompoundVertexControl, ObservableCollection<VertexControl>>(
                                                                                                          nameof(Vertices), x => x._vertices);

        #endregion

        #region LayoutMode

        /// <summary>
        /// Layout mode.
        /// </summary>
        public CompoundVertexInnerLayoutType LayoutMode
        {
            get => GetValue(LayoutModeProperty);
            set => SetValue(LayoutModeProperty, value);
        }

        /// <summary>
        /// Layout mode dependency property.
        /// </summary>
        [NotNull]
        public static readonly StyledProperty<CompoundVertexInnerLayoutType> LayoutModeProperty
            = AvaloniaProperty.Register<CompoundVertexControl, CompoundVertexInnerLayoutType>(nameof(LayoutMode));

        #endregion

        #region IsExpanded

        /// <summary>
        /// Indicates if vertex is expanded or not.
        /// </summary>
        public bool IsExpanded
        {
            get => GetValue(IsExpandedProperty);
            set => SetValue(IsExpandedProperty, value);
        }

        /// <summary>
        /// Is expanded dependency property.
        /// </summary>
        [NotNull]
        public static readonly StyledProperty<bool> IsExpandedProperty
            = AvaloniaProperty.Register<CompoundVertexControl,bool>(
                                        nameof(IsExpanded),
                                        true);

        private static void OnIsExpandedPropertyChanged(AvaloniaPropertyChangedEventArgs<bool> args)
        {
            var compoundVertexControl = (CompoundVertexControl)args.Sender;
            if (args.GetNewValue<bool>())
            {
                compoundVertexControl.RaiseEvent(new RoutedEventArgs(ExpandedEvent, compoundVertexControl));
            }
            else
            {
                compoundVertexControl.RaiseEvent(new RoutedEventArgs(CollapsedEvent, compoundVertexControl));
            }
        }

        #endregion

        private static void OnPositionChanged([NotNull] object sender, [NotNull] PositionChangedEventArgs args)
        {
            var compoundVertexControl = args.Source as CompoundVertexControl;
            if (compoundVertexControl is null || compoundVertexControl._activePositionChangeReaction)
                return;

            compoundVertexControl._activePositionChangeReaction = true;

            OnVertexMove(compoundVertexControl, args);

            compoundVertexControl._activePositionChangeReaction = false;
        }

        private static void OnVertexMove([NotNull] CompoundVertexControl compoundVertexControl, [NotNull] PositionChangedEventArgs args)
        {
            // We are moving the compound vertex itself
            if (compoundVertexControl == args.Source)
            {
                // Move the children with the same amount
                foreach (var childVertexControl in compoundVertexControl.Vertices)
                {
                    GraphCanvas.SetX(childVertexControl, GraphCanvas.GetX(childVertexControl) + args.XChange);
                    GraphCanvas.SetY(childVertexControl, GraphCanvas.GetY(childVertexControl) + args.YChange);
                }
            }
            else
            {
                // We are moving the parent or one of it's child
                var childVertexControl = args.Source as VertexControl;
                if (childVertexControl is null)
                    return;

                if (compoundVertexControl.Vertices.Contains(childVertexControl))
                {
                    // Update the position of all child vertices
                    foreach (var control in compoundVertexControl.Vertices)
                    {
                        if (control == childVertexControl)
                            continue;

                        var childCenterPos           = new aPoint(control.Bounds.Width / 2, control.Bounds.Height / 2);
                        var translatedChildCenterPos = control.TranslatePoint(childCenterPos, control.RootCanvas) ?? default(aPoint);

                        GraphCanvas.SetX(control, translatedChildCenterPos.X - control.RootCanvas.Translation.X);
                        GraphCanvas.SetY(control, translatedChildCenterPos.Y - control.RootCanvas.Translation.Y);
                    }
                }
            }
        }

        #endregion

        #region ICompoundVertexControl

        /// <inheritdoc />
        public Size InnerCanvasSize => InnerCanvas is null
            ? default(Size)
            : new Size(InnerCanvas.Bounds.Width, InnerCanvas.Bounds.Height);

        /// <inheritdoc />
        public Thickness VertexBorderThickness
        {
            get
            {
                if (InnerCanvas is null)
                    return default(Thickness);

                var innerCanvasPosition = InnerCanvas.TranslatePoint(default(aPoint), this) ?? default(aPoint);
                var innerCanvasSize     = InnerCanvasSize;
                var size                = new Size(Bounds.Width, Bounds.Height);

                // Calculate the thickness
                return new Thickness(
                                     innerCanvasPosition.X,
                                     innerCanvasPosition.Y,
                                     size.Width  - (innerCanvasPosition.X + innerCanvasSize.Width),
                                     size.Height - (innerCanvasPosition.Y + innerCanvasSize.Height));
            }
        }

        #region Routed Events

        /// <summary>
        /// Expanded event.
        /// </summary>
        [NotNull]
        public static readonly RoutedEvent ExpandedEvent
            = RoutedEvent.Register<CompoundVertexControl,RoutedEventArgs>(
                                                                          "Expanded",
                                                                          RoutingStrategies.Bubble);

        /// <inheritdoc />
        public event EventHandler<RoutedEventArgs> Expanded
        {
            add => AddHandler(ExpandedEvent, value);
            remove => RemoveHandler(ExpandedEvent, value);
        }

        /// <summary>
        /// Collapsed event.
        /// </summary>
        [NotNull]
        public static readonly RoutedEvent CollapsedEvent
            = RoutedEvent.Register<CompoundVertexControl,RoutedEventArgs>(
                                                                          "Collapsed",
                                                                          RoutingStrategies.Bubble);

        /// <inheritdoc />
        public event EventHandler<RoutedEventArgs> Collapsed
        {
            add => AddHandler(CollapsedEvent, value);
            remove => RemoveHandler(CollapsedEvent, value);
        }

        #endregion

        #endregion
    }
}